<?php
namespace Services;
/**
 * File holds class for object relational mapping
 *
 * PHP version 5.3.5
 *
 * @category   CM
 * @package    Services
 * @subpackage -
 * @author     markus karileet <markuskarileet@hotmail.com>
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL
 * @link       -
 */

class ORM {
	/**
	 * ORM method for object insertion to database
	 * @param Object $object
	 * @return int Insert ID when successful
	 * @throws Exception when problems inserting object
	 */
	public function insert($object) {
		//read obj properties, 
		$props = $object->getProps();
		$dbKeys = array();
		$objValues = array();
		//get values from properties
		foreach (array_keys($props) as $prop) {
			if (!in_array($prop, $this->_getExcludes())) {
				$key = substr($prop, 1);
				$dbKeys[] = "`$key`";
				$getter = "get".ucfirst($key);
				$objValues[] = "'".$object->$getter()."'";
			}
		}
		//generate sql
		$sql = 'INSERT INTO `%s` (%s) VALUES (%s);';
		$preparedSql = sprintf(
            $sql, 
            $this->_getTableName($object), 
            implode(',', $dbKeys), 
            implode(',', $objValues)
        );
		//execute sql
		$link = \Services\Database::singleton()->getConnection();
		if (mysql_query($preparedSql, $link)) {
			$retval = mysql_insert_id($link);
		} else {
			throw new \Exception('Unable to insert object! ' . mysql_error($link));
		}
		return $retval;
	}
	
	/**
	 * Method updates object by ID. All fields are updated
	 * @param Object $object
	 * @return boolean true on success, false on error
	 * @throws \Exception When problem occurred while updating
	 */
	public function update($object) {
		$sql = 'UPDATE `%s` SET %s WHERE `id` = %d';
		$retval = false;
		$keyValues = $this->_getKeyEqualsValueFromObject($object);
		$preparedSql = sprintf(
            $sql, 
            $this->_getTableName($object), 
            implode(', ', $keyValues), 
            $object->getId()
        );
		$link = \Services\Database::singleton()->getConnection();
		if ($retval = mysql_query($preparedSql, $link)) {
			$retval = true;
		} else {
			throw new \Exception('Unable to update object! ' . mysql_error($link));
		}
		return $retval;
	}
	
	/**
	 * Method selects object from database. Pre-set object values to use AND 
	 * comparisson between them.
	 * @param Object $object
	 * @return Object on success, false when not found
	 * @throws \Exception When problem selecting object
	 */
	public function select($object) {
		$arrayOfObjects = $this->selectMulti($object);
		$obj = false;
		if ($arrayOfObjects) {
			$obj = array_pop($arrayOfObjects);
		}
		return $obj; 
	}
	
	/**
	 * Method selects multiple rows. Useful when selecting by foreign key!
	 * @param Object $object
	 * @return array Array of objects, false when nothing found
	 * @throws \Exception When problem selecting object
	 */
	public function selectMulti($object) {
		$sql = 'SELECT * FROM `%s` WHERE %s';
		$keyValues = $this->_getKeyEqualsValueFromObject($object);
		if (count($keyValues) == 0) {
			$keyValues[] = '1 = 1';
		}
		$preparedSql = sprintf(
            $sql, 
            $this->_getTableName($object), 
            implode(' AND ', $keyValues)
        );
		$out = $this->rawQuery($preparedSql, $object);
		return $out;
	}
	
	/**
	 * Method executes a prepared sql, no matter what it is creates an array
	 * of objects with the result
	 * @param var $preparedSql SQL statement
	 * @param Object $object Object type to use, clone
	 * @return array Array of $object found from database 
	 * @throws \Exception
	 */
	public function rawQuery($preparedSql, $object) {
		$link = \Services\Database::singleton()->getConnection();
		$out = false;
		if ($retval = mysql_query($preparedSql, $link)) {
			while ($resultset = mysql_fetch_assoc($retval)) {
				$o = clone $object;
				foreach (array_keys($resultset) as $key) {
					$setter = "set".ucfirst($key);
					$o->$setter($resultset[$key]);
				}
				$out[] = $o;
			}
		} else {
			throw new \Exception('Unable to query object! ' . mysql_error($link));
		}
		return $out;
	}
	
	/**
	 * Method deletes object from database.Pre-set object values to use AND 
	 * comparisson between them.
	 * @param Object $object
	 * @throws \Exception When delete fails
	 * @return boolean True on success, false on error
	 */
	public function delete($object) {
		$sql = 'DELETE FROM `%s` WHERE %s';
		$retval = false;
		$keyValues = $this->_getKeyEqualsValueFromObject($object);
		$preparedSql = sprintf(
            $sql, 
            $this->_getTableName($object), 
            implode(' AND ', $keyValues)
        );
		$link = \Services\Database::singleton()->getConnection();
		if ($retval = mysql_query($preparedSql, $link)) {
			$retval = true;
		} else {
			throw new \Exception('Unable to delete object! ' . mysql_error($link));
		}
		return $retval;
	}
	
	/**
	 * Method returns table name from classname
	 * @param Object $object
	 * @return var Table name
	 */
	private function _getTableName($object) {
		$table = explode('\\', get_class($object));
		return lcfirst($table[1]);
	}
	
	/**
	 * Method reads object properties that are not in the excludes list and 
	 * returns array like array(`column` = 'object value')
	 * @param Object $object
	 * @return array
	 */
	private function _getKeyEqualsValueFromObject($object) {
		$props = $object->getProps();
		$keyValues = array();
		foreach (array_keys($props) as $prop) {
			if (!in_array($prop, $this->_getExcludes())) {
				$key = substr($prop, 1);
				$getter = "get".ucfirst($key);
				if ($object->$getter() != null) {
					$keyValues[] = "`$key` = '".$object->$getter()."'";
				}
			}
		}
		return $keyValues;
	}
	
	/**
	 * Method returns excludes fieldnames
	 * @return array
	 */ 
	private function _getExcludes() {
		return  array('_className');
	}	
}